﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Threading;
using TipoDatos;
using P2P.Clases;
using Server;

namespace P2P
{
    public partial class FormP2P : Form
    {
        private Thread tServer;
        private Thread tClient;
        private TcpClient tcpc;
        private Stream s;
        private delegate void updateUI(String data);
        private delegate void updateUIB(Byte[] data);
        private delegate void updateUIH();
        private Int64 nClientID = 0;
        private Byte[] ClientID = new Byte[4];
        private String ip = "";
        private int puerto = 0;
        private string datosRecibidos;

        public FormP2P()
        {
            InitializeComponent();
        }

        private void WriteLog(String data)
        {
            if (this.InvokeRequired == false)
            {
                log.Text = data + Environment.NewLine + log.Text;
                log.Items.Add(data);
            }
            else
            {
                updateUI updateMethod = new updateUI(this.WriteLog);
                this.Invoke(updateMethod, new object[] { data });
            }
        }

        private void WriteLog(Byte[] data)
        {
            if (this.InvokeRequired == false)
            {
                String cdata = "";

                for (int i = 0; i < data.Length; i++)
                    if (data[i].ToString("X").Length < 2)
                        cdata += "0" + data[i].ToString("X") + " ";
                    else
                        cdata += data[i].ToString("X") + " ";

                log.Text = cdata + Environment.NewLine + log.Text;
                log.Items.Add(cdata);
            }
            else
            {
                updateUIB updateMethod = new updateUIB(this.WriteLog);
                this.Invoke(updateMethod, new object[] { data });
            }
        }

        public Stream ConvertByteArrayToStream(byte[] input)
        {
            return new MemoryStream(input);
        }

        private void WriteHash(Byte[] data)
        {
            if (this.InvokeRequired == false)
            {
                String cdata = "";
                Int64 tam = 0;

                if (data[5] == Convert.ToInt16(tOpcode.OP_SEARCHRESULT))
                    tam = cFunciones.ObtenerTam(data) + 5;
                else
                    tam = data.Length;

                if (tam > data.Length) tam = data.Length;

                for (int i = 0; i < tam; i++)
                    if (data[i].ToString("X").Length < 2)
                        cdata += "0" + data[i].ToString("X") + " ";
                    else
                        cdata += data[i].ToString("X") + " ";

                //hashes.Text += cdata;
                datosRecibidos += cdata;
            }
            else
            {
                updateUIB updateMethod = new updateUIB(this.WriteHash);
                this.Invoke(updateMethod, new object[] { data });
            }
        }

        private void AnalizarHashes()
        {
            if (this.InvokeRequired == false)
            {
                string[] datos;
                String cad = "";

                //if (!hashes.Text.StartsWith("E3"))
                if (!datosRecibidos.StartsWith("E3"))
                {
                    //datos = hashes.Text.Split(' ');
                    datos = datosRecibidos.Split(' ');
                    byte[] comprimido = new byte[datos.Length - 6];
                    byte[] descomprimido = new byte[100000];
                    int tamDesc = 100000;

                    for (int i = 6; i < datos.Length; i++)
                    {
                        if (datos[i] != "")
                            comprimido[i - 6] = Convert.ToByte(Int64.Parse(datos[i], System.Globalization.NumberStyles.HexNumber));
                    }

                    ZLib.uncompress(descomprimido, ref tamDesc, comprimido, comprimido.Length);

                    for (int j = descomprimido.Length - 1; j > 0; j--)
                        if (descomprimido[j] == 0) tamDesc--;
                        else j = 0;

                    String tamReal = tamDesc.ToString("X").PadLeft(8, '0');
                    String v1 = tamReal.Substring(6, 2);
                    String v2 = tamReal.Substring(4, 2);
                    String v3 = tamReal.Substring(2, 2);
                    String v4 = tamReal.Substring(0, 2);
                    cad = "E3 " + v1 + " " + v2 + " " + v3 + " " + v4 + " 33 ";

                    for (int i = 0; i < tamDesc; i++)
                        if (descomprimido[i].ToString("X").Length < 2)
                            cad += "0" + descomprimido[i].ToString("X") + " ";
                        else
                            cad += descomprimido[i].ToString("X") + " ";

                    cad = cad.ToUpper();
                }
                else
                    cad = datosRecibidos;

                detail.Text = cad;

                datos = cad.Split(' ');

                Int64 nFicheros = 0;

                if (datos.Length > 8)
                    cFunciones.ObtenerNFicheros(datos);

                string[] lHashes = new string[nFicheros];
                string[] lClientID = new string[nFicheros];
                string[] lClientPort = new string[nFicheros];
                string[] lNombreFile = new string[nFicheros];

                if (nFicheros > 0)
                {
                    cad = cad.Substring(30);
                    int pos = 0;
                    int cont = 0;

                    while ((pos = cad.IndexOf("82 01")) > 77 && cont < nFicheros)
                    {
                        if (pos > 77)
                        {
                            lHashes[cont] = cad.Substring(pos - 78, 47).Replace(" ", "");
                            lClientID[cont] = cad.Substring(pos - 31, 12).Replace(" ", "");
                            lClientPort[cont] = cad.Substring(pos - 19, 6).Replace(" ", "");
                            String cTamFile = cad[pos + 9].ToString() + cad[pos + 10].ToString() + cad[pos + 6].ToString() + cad[pos + 7].ToString();

                            if (cTamFile.StartsWith("0"))
                            {
                                int tamFile = Int32.Parse(cTamFile, System.Globalization.NumberStyles.HexNumber);
                                String nFile = cad.Substring(pos + 12, (tamFile * 3) - 1).Replace(" ", "");
                                lNombreFile[cont] = HexString2Ascii(nFile);
                                cont++;
                            }

                            cad = cad.Substring(pos + 3);
                        }
                    }
                }

                //String tmp = "";
                //datos = cad.Split(' ');

                //for (int i = 0; i < nFicheros; i++)
                //{
                //    tmp = "";
                    
                //    for (int j = 0; j < 16; j++)
                //        tmp += datos[j];

                //    lHashes[i] = tmp;
                //    tmp = "";

                //    for (int j = 16; j < 20; j++)
                //        tmp += datos[j];

                //    lClientID[i] = tmp;
                //    tmp = "";

                //    for (int j = 20; j < 22; j++)
                //        tmp += datos[j];

                //    lClientPort[i] = tmp;
                //}

                hashes.Text = "";

                for (int i = 0; i < lHashes.Length; i++)
                {
                    hashes.Text += Environment.NewLine;
                    hashes.Text += "Hash: " + lHashes[i];
                    hashes.Text += Environment.NewLine;

                    hashes.Text += "Client ID: " + lClientID[i];
                    hashes.Text += Environment.NewLine;

                    hashes.Text += "Client Port: " + lClientPort[i];
                    hashes.Text += Environment.NewLine;

                    hashes.Text += "Filename: " + lNombreFile[i];
                    hashes.Text += Environment.NewLine;
                }
            }
            else
            {
                updateUIH updateMethod = new updateUIH(this.AnalizarHashes);
                this.Invoke(updateMethod, new object[] { });
            }
        }

        private string HexString2Ascii(string hexString)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i <= hexString.Length - 2; i += 2)
            {
                sb.Append(Convert.ToString(Convert.ToChar(Int32.Parse(hexString.Substring(i, 2), System.Globalization.NumberStyles.HexNumber))));
            }
            return sb.ToString();
        }

        private void bConectar_Click(object sender, EventArgs e)
        {
            if (host.Text == "")
                MessageBox.Show("Debes escribir un host");
            else
                if (!host.Text.Contains(":"))
                    MessageBox.Show("Debes escribir host:puerto");
                else
                    if (search.Text == "")
                        MessageBox.Show("Debes escribir una cadena de búsqueda");
                    else
                    {
                        bConectar.Enabled = false;
                        bDesconectar.Enabled = true;
                        bBuscar.Enabled = true;
                        log.Items.Clear();
                        detail.Text = "";
                        hashes.Text = "";
                        datosRecibidos = "";

                        host.Text = host.Text.Trim();

                        tServer = new Thread(new ThreadStart(this.ListenServer));
                        tServer.Start();

                        string[] datos = host.Text.Split(':');
                        ip = datos[0];
                        puerto = Convert.ToInt16(datos[1]);

                        //Login(ip, puerto, search.Text); // (ip, puerto, cadena de busqueda)
                        tClient = new Thread(new ThreadStart(this.Login));
                        tClient.Start();
                    }
        }


        /***** LISTENER *****/
        // Una vez realizamos un Login contra un servidor ed2k éste hará una comprobación del puerto 23504 antes de
        // devulvernos un ClientID con el que poder trabajar.
        // Para responder a la petición creamos una tarea que escuche en ese puerto y al recibir la petición del
        // servidor de ed2k, responda con un Hello Answer.
        // Gracias a esto evitamos un 'Low ID Login' o un 'Reject Session Secuence' 

        #region Listener
        private void ListenServer()
        {
            Listen(tProtocol.eDonkey);
        }

        private void Listen(tProtocol protocol)
        {
            Byte[] data = cMensajes.HelloAnswer(tProtocol.eDonkey, cMensajes.Login(tProtocol.eDonkey, ClientID));

            try
            {
                TcpListener tcpListener = new TcpListener(23504);
                tcpListener.Start();
                Socket socketForClient = tcpListener.AcceptSocket();

                if (socketForClient.Connected)
                {
                    NetworkStream networkStream = new NetworkStream(socketForClient);

                    Byte[] read = new Byte[1024];

                    // Esperamos la conexión del servidor
                    networkStream.Read(read, 0, read.Length);

                    // Enviamos el paquete Hello Answer
                    networkStream.Write(data, 0, data.Length);

                    networkStream.Close();
                    socketForClient.Close();
                    tcpListener.Stop();

                    this.WriteLog("Server -> Client (23504): Hello Message");
                    this.WriteLog(read);
                    this.WriteLog("");

                    this.WriteLog("Client (23504) -> Server: Hello Answer");
                    this.WriteLog(data);
                    this.WriteLog("");
                }
                else
                {
                    socketForClient.Close();
                    tcpListener.Stop();
                }
            }
            catch { }
        }
        #endregion

        private void Login()
        {
            String server = ip;
            int port = puerto;
            String text = search.Text;

            try
            {
                // Resolvemos los datos del servidor
                tcpc = new TcpClient();
                Byte[] read = new Byte[2048];

                // Intentamos conectar con el servidor
                IPHostEntry IPHost = Dns.Resolve(server);
                string[] aliases = IPHost.Aliases;
                IPAddress[] addr = IPHost.AddressList;

                /**********                   PRIMER PASO                          **********/
                /**********                                                        **********/
                /********** Cliente  -> Servidor - Envío de un Login (Hello)       **********/
                /********** Servidor -> Cliente  - Posible envío de Server Message **********/
                /********** Servidor -> Cliente  - Respuesta con un ID Change      **********/

                // Creamos el mensaje para Login
                ClientID[0] = 0x54;
                ClientID[1] = 0x7f;
                ClientID[2] = 0x08;
                ClientID[3] = 0x45;
                Byte[] data = cMensajes.Login(tProtocol.eDonkey, ClientID);

                IPEndPoint ep = new IPEndPoint(addr[0], port);
                tcpc.Connect(ep);

                this.WriteLog("Client -> Server: Login Message");
                this.WriteLog(data);
                this.WriteLog("");

                // Enviamos el paquete Login
                s = tcpc.GetStream();
                s.Write(data, 0, data.Length);

                bool recepcion = false;

                // Leemos la respuesta del servidor y recibimos datos hasta obtener un Hello
                // (el servidor puede mandar en cualquier momento mensajes de servidor (Server Message) que
                // simplemente los mostramos por pantalla pero seguimos a la espera hasta llegar el Hello)
                while (recepcion == false)
                {
                    if (s.Read(read, 0, read.Length) != 0)
                    {
                        recepcion = DireccionarDatos(read);
                        read = new Byte[2048];
                    }
                }
            }
            catch
            {
                MessageBox.Show("Error de conexión");
                tcpc.Close();
                bConectar.Enabled = true;
                bDesconectar.Enabled = false;
                bBuscar.Enabled = false;
            }
        }

        private bool DireccionarDatos(byte[] read)
        {
            bool recepcion = false;
            bool salir = false;

            byte[] restante = read;

            while (salir == false)
            {
                Int64 tam = cFunciones.ObtenerTam(restante) + 5;
                byte[] mensaje = new byte[tam];

                for (int i = 0; i < tam; i++)
                    mensaje[i] = restante[i];

                if (mensaje.Length < 6) salir = true;
                else
                {
                    if (EscribeMensaje(mensaje) == true) recepcion = true;

                    read = restante;
                    restante = new byte[restante.Length - mensaje.Length];

                    for (int i = 0; i < restante.Length - mensaje.Length; i++)
                        restante[i] = read[i + mensaje.Length];
                }
            }

            return recepcion;
        }

        private bool EscribeMensaje(byte[] data)
        {
            bool recepcion = false;

            if (data[5] == Convert.ToInt16(tOpcode.OP_SERVERSTATUS)) // 0x34 - Server Status
                this.WriteLog("Server -> Client: Server Status");
            if (data[5] == Convert.ToInt16(tOpcode.OP_SERVERMESSAGE)) // 0x38 - Server Message
                this.WriteLog("Server -> Client: Server Message");
            if (data[5] == Convert.ToInt16(tOpcode.OP_IDCHANGE)) // 0x40 - ID Change
                this.WriteLog("Server -> Client: ID Change");

            this.WriteLog(data);

            if (data[5] == Convert.ToInt16(tOpcode.OP_SERVERSTATUS)) // 0x34 - Server Status
            {
                byte[] userCount = new byte[4];
                byte[] fileCount = new byte[4];

                for (int k = 6; k < 10; k++)
                    userCount[k - 6] = data[k];
                for (int k = 10; k < 14; k++)
                    fileCount[k - 10] = data[k];

                Int64 users = cFunciones.ObtenerId(userCount);
                Int64 files = cFunciones.ObtenerId(fileCount);
                this.WriteLog("User Count: " + users);
                this.WriteLog("File Count: " + files);

                recepcion = true;
            }

            if (data[5] == Convert.ToInt16(tOpcode.OP_SERVERMESSAGE)) // 0x38 - Server Message
            {
                Byte[] message = new Byte[1024];

                for (int k = 8; k < data.Length; k++)
                    message[k - 8] = data[k];

                Encoding enc = Encoding.ASCII;
                this.WriteLog("Message: " + enc.GetString(message));
            }

            if (data[5] == Convert.ToInt16(tOpcode.OP_IDCHANGE)) // 0x40 - ID Change
            {
                for (int k = 6; k < 10; k++)
                    ClientID[k - 6] = data[k];

                nClientID = cFunciones.ObtenerId(ClientID);
                this.WriteLog("Client ID: " + nClientID);

                recepcion = true;
            }

            this.WriteLog("");

            return recepcion;
        }

        private void bSalir_Click(object sender, EventArgs e)
        {
            try { tServer.Abort(); }
            catch { }

            try { tClient.Abort(); }
            catch { }

            try { tcpc.Close(); }
            catch { }          

            Close();
        }

        private void log_SelectedIndexChanged(object sender, EventArgs e)
        {
            detail.Text = log.Items[log.SelectedIndex].ToString();
        }

        private void FormP2P_Load(object sender, EventArgs e)
        {
            bConectar.Enabled = true;
            bDesconectar.Enabled = false;
            bBuscar.Enabled = false;
            
            host.Text = "88.80.28.48:4321";
            search.Text = "alicante";
        }

        private void bBuscar_Click(object sender, EventArgs e)
        {
            /**********                   SEGUNDO PASO                         **********/
            /**********                                                        **********/
            /********** Cliente  -> Servidor - Envío de un Search Request      **********/
            /********** Servidor -> Cliente  - Respuesta con un Search Answer  **********/

            detail.Text = "";
            hashes.Text = "";
            datosRecibidos = "";

            Byte[] read = new Byte[2048];
            byte[] data = cMensajes.Search(tProtocol.eDonkey, search.Text);

            this.WriteLog("Client -> Server: Search Request");
            this.WriteLog(data);
            this.WriteLog("");

            s.Write(data, 0, data.Length);

            bool inicio = true;
            Int64 tam = 0;
            Int64 cont = 0;

            while (cont <= tam)
            {
                if (s.Read(read, 0, read.Length) != 0)
                {
                    cont += read.Length;

                    if (inicio == true)
                    {
                        tam = cFunciones.ObtenerTam(read) + 5;

                        if (read[5] == Convert.ToInt16(tOpcode.OP_SEARCHRESULT)) // 0x33 - Search Answer
                            this.WriteLog("Server -> Client: Search Answer");
                    }

                    this.WriteHash(read);

                    read = new Byte[2048];
                    inicio = false;
                }
            }

            this.AnalizarHashes();
        }

        private void bDesconectar_Click(object sender, EventArgs e)
        {
            tcpc.Close();
            bConectar.Enabled = true;
            bDesconectar.Enabled = false;
            bBuscar.Enabled = false;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (textBox1.Text == "")
            {
                textBox1.Text = "A44A2A8143DE78CAA23EA173BB9640BD";
                textBox2.Text = "02E63802";
            }

            String v1 = textBox1.Text[0].ToString() + textBox1.Text[1].ToString();
            String v2 = textBox1.Text[2].ToString() + textBox1.Text[3].ToString();
            String v3 = textBox1.Text[4].ToString() + textBox1.Text[5].ToString();
            String v4 = textBox1.Text[6].ToString() + textBox1.Text[7].ToString();
            String v5 = textBox1.Text[8].ToString() + textBox1.Text[9].ToString();
            String v6 = textBox1.Text[10].ToString() + textBox1.Text[11].ToString();
            String v7 = textBox1.Text[12].ToString() + textBox1.Text[13].ToString();
            String v8 = textBox1.Text[14].ToString() + textBox1.Text[15].ToString();
            String v9 = textBox1.Text[16].ToString() + textBox1.Text[17].ToString();
            String v10 = textBox1.Text[18].ToString() + textBox1.Text[19].ToString();
            String v11 = textBox1.Text[20].ToString() + textBox1.Text[21].ToString();
            String v12 = textBox1.Text[22].ToString() + textBox1.Text[23].ToString();
            String v13 = textBox1.Text[24].ToString() + textBox1.Text[25].ToString();
            String v14 = textBox1.Text[26].ToString() + textBox1.Text[27].ToString();
            String v15 = textBox1.Text[28].ToString() + textBox1.Text[29].ToString();
            String v16 = textBox1.Text[30].ToString() + textBox1.Text[31].ToString();

            byte[] hash = new byte[16];
            hash[0] = Convert.ToByte(Int32.Parse(v1, System.Globalization.NumberStyles.HexNumber));
            hash[1] = Convert.ToByte(Int32.Parse(v2, System.Globalization.NumberStyles.HexNumber));
            hash[2] = Convert.ToByte(Int32.Parse(v3, System.Globalization.NumberStyles.HexNumber));
            hash[3] = Convert.ToByte(Int32.Parse(v4, System.Globalization.NumberStyles.HexNumber));
            hash[4] = Convert.ToByte(Int32.Parse(v5, System.Globalization.NumberStyles.HexNumber));
            hash[5] = Convert.ToByte(Int32.Parse(v6, System.Globalization.NumberStyles.HexNumber));
            hash[6] = Convert.ToByte(Int32.Parse(v7, System.Globalization.NumberStyles.HexNumber));
            hash[7] = Convert.ToByte(Int32.Parse(v8, System.Globalization.NumberStyles.HexNumber));
            hash[8] = Convert.ToByte(Int32.Parse(v9, System.Globalization.NumberStyles.HexNumber));
            hash[9] = Convert.ToByte(Int32.Parse(v10, System.Globalization.NumberStyles.HexNumber));
            hash[10] = Convert.ToByte(Int32.Parse(v11, System.Globalization.NumberStyles.HexNumber));
            hash[11] = Convert.ToByte(Int32.Parse(v12, System.Globalization.NumberStyles.HexNumber));
            hash[12] = Convert.ToByte(Int32.Parse(v13, System.Globalization.NumberStyles.HexNumber));
            hash[13] = Convert.ToByte(Int32.Parse(v14, System.Globalization.NumberStyles.HexNumber));
            hash[14] = Convert.ToByte(Int32.Parse(v15, System.Globalization.NumberStyles.HexNumber));
            hash[15] = Convert.ToByte(Int32.Parse(v16, System.Globalization.NumberStyles.HexNumber));

            String w4 = textBox2.Text[0].ToString() + textBox2.Text[1].ToString();
            String w3 = textBox2.Text[2].ToString() + textBox2.Text[3].ToString();
            String w2 = textBox2.Text[4].ToString() + textBox2.Text[5].ToString();
            String w1 = textBox2.Text[6].ToString() + textBox2.Text[7].ToString();

            byte[] size = new byte[4];
            size[0] = Convert.ToByte(Int32.Parse(w1, System.Globalization.NumberStyles.HexNumber));
            size[1] = Convert.ToByte(Int32.Parse(w2, System.Globalization.NumberStyles.HexNumber));
            size[2] = Convert.ToByte(Int32.Parse(w3, System.Globalization.NumberStyles.HexNumber));
            size[3] = Convert.ToByte(Int32.Parse(w4, System.Globalization.NumberStyles.HexNumber));

            //Byte[] data = cMensajes.GetSources(tProtocol.eDonkey, hash, size);
            //Byte[] data = cMensajes.GetSources(tProtocol.eDonkey, hash);
            Byte[] data = cMensajes.FileRequest(tProtocol.eDonkey, hash);
            Byte[] read = new Byte[2048];

            //this.WriteLog("Client -> Server: Get Sources");
            this.WriteLog("Client -> Server: File request");
            this.WriteLog(data);
            this.WriteLog("");

            s.Write(data, 0, data.Length);

            if (s.Read(read, 0, read.Length) != 0)
            {
                if (read[5] == Convert.ToInt16(tOpcode.OP_FOUNDSOURCES)) // 0x42 - Found Sources
                    this.WriteLog("Server -> Client: Found Sources");
                if (read[5] == Convert.ToInt16(tOpcode.OP_SERVERMESSAGE)) // 0x38 - Server Message
                    this.WriteLog("Server -> Client: Server Message");

                this.WriteLog(read);

                if (read[5] == Convert.ToInt16(tOpcode.OP_SERVERMESSAGE)) // 0x38 - Server Message
                {
                    Byte[] message = new Byte[2048];

                    for (int k = 8; k < read.Length; k++)
                        message[k - 8] = read[k];

                    Encoding enc = Encoding.ASCII;
                    this.WriteLog("Message: " + enc.GetString(message));
                }
            }
        }
    }
}
